---
title: DRY原则
description: 深入理解DRY（Don't Repeat Yourself）原则及其在软件开发中的应用
sidebar_position: 2
---

# DRY原则

## 什么是DRY原则

DRY（Don't Repeat Yourself）原则是由Andy Hunt和Dave Thomas在《程序员修炼之道》一书中提出的软件开发原则。其核心思想是：

> 系统中的每一个功能都应该有唯一的、权威的、明确的表述。

这个原则强调：
- 避免代码重复
- 避免知识重复
- 避免意图重复

## 常见误解

1. **过度追求代码复用**
   - DRY不等于消除所有重复的代码
   - 重点是消除知识和意图的重复
   - 有时适度的代码重复反而有利于系统维护

以下是一个过度追求代码复用的例子：

```java
// 过度追求复用的错误示例
public class CommonUtils {
    public static BigDecimal calculate(String type, BigDecimal amount) {
        if ("orderDiscount".equals(type)) {
            return amount.multiply(new BigDecimal("0.9")); // 订单9折
        } else if ("memberDiscount".equals(type)) {
            return amount.multiply(new BigDecimal("0.9")); // 会员9折
        }
        return amount;
    }
}

// 虽然代码看起来相同，但业务含义不同，强行复用反而增加了耦合
```

更好的做法是根据业务含义分开处理：

```java
public class OrderService {
    private static final BigDecimal ORDER_DISCOUNT = new BigDecimal("0.9");
    
    public BigDecimal calculateOrderDiscount(BigDecimal amount) {
        // 订单折扣可能会随营销策略调整
        return amount.multiply(ORDER_DISCOUNT);
    }
}

public class MemberService {
    private static final BigDecimal MEMBER_DISCOUNT = new BigDecimal("0.9");
    
    public BigDecimal calculateMemberDiscount(BigDecimal amount) {
        // 会员折扣可能会根据会员等级变化
        return amount.multiply(MEMBER_DISCOUNT);
    }
}
```

2. **混淆表象重复和实质重复**
   - 表象重复：代码看起来相似，但背后的业务含义不同
   - 实质重复：代码不仅相似，且表达相同的业务含义
   - DRY原则针对的是实质重复

## 正确应用DRY原则

### 1. 识别真正的重复

在以下情况下，即使代码看起来相似，也不应该强行复用：
- 不同的业务规则
- 不同的变化方向
- 不同的变化频率

下面是一个识别和处理真正重复的例子：

```java
// 存在实质重复的代码
public class UserValidator {
    public boolean validateUsername(String username) {
        return username != null && username.length() >= 3 && username.length() <= 20;
    }
}

public class ProductValidator {
    public boolean validateProductName(String productName) {
        return productName != null && productName.length() >= 3 && productName.length() <= 20;
    }
}

// 提取真正的重复逻辑
public class StringValidator {
    public static boolean validateLength(String value, int min, int max) {
        return value != null && value.length() >= min && value.length() <= max;
    }
}

// 使用提取的通用逻辑
public class UserValidator {
    public boolean validateUsername(String username) {
        return StringValidator.validateLength(username, 3, 20);
    }
}

public class ProductValidator {
    public boolean validateProductName(String productName) {
        return StringValidator.validateLength(productName, 3, 20);
    }
}
```

### 2. 选择合适的抽象层次

根据重复的类型选择合适的抽象方式：
- 代码层面：提取公共方法、基类
- 设计层面：使用设计模式
- 架构层面：微服务、组件化

下面是一个选择合适抽象层次的例子：

```java
// 抽象基类处理通用逻辑
public abstract class BaseEntity {
    private Long id;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    
    // 通用的审计字段处理
    public void prePersist() {
        this.createTime = LocalDateTime.now();
        this.updateTime = this.createTime;
    }
    
    public void preUpdate() {
        this.updateTime = LocalDateTime.now();
    }
    
    // getter and setter
}

// 具体实体类
public class User extends BaseEntity {
    private String username;
    private String email;
    
    // 特定于User的业务逻辑
}

public class Product extends BaseEntity {
    private String name;
    private BigDecimal price;
    
    // 特定于Product的业务逻辑
}
```

### 3. 使用设计模式

合理运用设计模式来消除重复：
- 模板方法模式：处理算法骨架重复
- 策略模式：处理算法实现重复
- 装饰器模式：处理功能叠加重复
- 观察者模式：处理交互逻辑重复

以下是使用模板方法模式消除重复的例子：

```java
// 使用模板方法模式处理导出功能
public abstract class DataExporter {
    // 模板方法定义导出流程
    public final void export() {
        beforeExport();
        doExport();
        afterExport();
    }
    
    // 钩子方法，允许子类自定义前置处理
    protected void beforeExport() {}
    
    // 抽象方法，子类必须实现具体的导出逻辑
    protected abstract void doExport();
    
    // 钩子方法，允许子类自定义后置处理
    protected void afterExport() {}
}

// Excel导出实现
public class ExcelExporter extends DataExporter {
    @Override
    protected void doExport() {
        // Excel特定的导出实现
    }
}

// PDF导出实现
public class PdfExporter extends DataExporter {
    @Override
    protected void doExport() {
        // PDF特定的导出实现
    }
    
    @Override
    protected void afterExport() {
        // PDF导出后的特殊处理
    }
}
```

### 4. 持续重构

- 及时识别和消除重复
- 保持代码的整洁和可维护性
- 在添加新功能时注意是否引入重复

## 平衡代码重复和可维护性

### 1. 可以接受重复的场景

1. **简单的重复**
   - 代码量很小
   - 重复不会造成维护负担
   - 强行抽象反而增加复杂度

2. **不相关的重复**
   - 业务含义不同
   - 变化方向不同
   - 生命周期不同

3. **临时的重复**
   - 在重构过程中
   - 在原型开发阶段
   - 作为过渡方案

### 2. 不能容忍的重复

1. **知识重复**
   - 业务规则重复
   - 配置信息重复
   - 依赖关系重复

2. **结构重复**
   - 类层次结构重复
   - 模块组织重复
   - 架构模式重复

3. **数据重复**
   - 数据定义重复
   - 数据存储重复
   - 数据传输重复

## 最佳实践

1. **理解业务含义**
   - 深入理解业务逻辑
   - 识别真正的重复
   - 区分表象重复和实质重复

2. **选择合适的抽象级别**
   - 不过度抽象
   - 保持代码的可读性
   - 考虑维护成本

3. **持续重构**
   - 保持代码整洁
   - 及时消除重复
   - 控制技术债务

4. **使用合适的工具和技术**
   - 代码分析工具
   - 重构工具
   - 版本控制系统

5. **建立团队规范**
   - 代码审查标准
   - 重构决策流程
   - 技术债务管理

6. **关注变化方向**
   - 预测可能的变化
   - 评估抽象的价值
   - 权衡利弊得失

## 总结

DRY原则的核心是消除知识和意图的重复，而不是简单地消除代码重复。在实践中，需要：

1. 正确理解DRY原则的本质
2. 在消除重复和保持代码质量之间找到平衡
3. 根据实际情况选择合适的抽象级别
4. 持续关注和调整抽象决策
